package com.dianping.localcache;

import java.util.concurrent.atomic.AtomicInteger;

import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;

import com.dennytech.tac.utils.DataCursor;
import com.dennytech.tac.utils.DatabaseCursor;

public class CacheInfoManagerImpl implements CacheInfoManager {
	private static final String FD_URL = "K";
	private static final String FD_VAL = "V"; // create(32bit) | size(32bit),
	// used for fast query
	private static final String FD_SIZE = "S"; // size for sum use
	private static final String FD_CREATE = "C";

	private SQLiteDatabase db;
	private final String cacheName;
	private final AtomicInteger size = new AtomicInteger();
	private final AtomicInteger count = new AtomicInteger();
	private SQLiteStatement queryStat, deleteStat, clearStat;
	private DatabaseUtils.InsertHelper insertHelper;
	private int iUrl, iVal, iSize, iCreate;

	public CacheInfoManagerImpl(SQLiteDatabase db, String cacheName) {
		this.db = db;
		this.cacheName = cacheName;

		StringBuilder sb = new StringBuilder();
		sb.append("CREATE TABLE IF NOT EXISTS ").append(cacheName).append(" (");
		sb.append(FD_URL).append(" TEXT PRIMARY KEY, ");
		sb.append(FD_VAL).append(" INT8, ");
		sb.append(FD_SIZE).append(" INT, ");
		sb.append(FD_CREATE).append(" INT);");

		db.execSQL(sb.toString());

		Cursor c = db.rawQuery("SELECT COUNT(*) FROM " + cacheName, null);
		try {
			if (c.moveToFirst()) {
				count.set(c.getInt(0));
			}
		} finally {
			c.close();
		}

		c = db.rawQuery("SELECT SUM(" + FD_SIZE + ") as sum FROM " + cacheName,
			null);
		try {
			if (c.moveToFirst()) {
				size.set(c.getInt(0));
			}
		} finally {
			c.close();
		}

		queryStat = db.compileStatement("SELECT " + FD_VAL + " FROM "
				+ cacheName + " WHERE " + FD_URL + "=?");

		deleteStat = db.compileStatement("DELETE FROM " + cacheName + " WHERE "
				+ FD_URL + "=?");

		clearStat = db.compileStatement("DELETE FROM " + cacheName + " WHERE 1");

		insertHelper = new DatabaseUtils.InsertHelper(db, cacheName);
		iUrl = insertHelper.getColumnIndex(FD_URL);
		iVal = insertHelper.getColumnIndex(FD_VAL);
		iSize = insertHelper.getColumnIndex(FD_SIZE);
		iCreate = insertHelper.getColumnIndex(FD_CREATE);
	}

	private CacheInfo decode(String url, long l) {
		long create = (l >>> 32) * 1000;
		int size = (int) (l & 0x00000000FFFFFFFFL);
		return new CacheInfo(url, size, create);
	}

	private long encode(CacheInfo ci) {
		long l = (long) (0x00000000FFFFFFFFL & (ci.create / 1000));
		l = (l << 32) | ci.size;
		return l;
	}

	private int trimCreate(long c) {
		return (int) (0x00000000FFFFFFFFL & (c / 1000));
	}

	/*private long getCreate(int c) {
		return 1000L * (long) (0x00000000FFFFFFFFL & c);
	}*/

	@Override
	public CacheInfo query(String url) {
		if (db == null)
			return null;
		synchronized (queryStat) {
			queryStat.bindString(1, url);
			try {
				long l = queryStat.simpleQueryForLong();
				return decode(url, l);
			} catch (SQLiteException e) {
				return null;
			}
		}
	}

	@Override
	public boolean delete(CacheInfo cacheInfo) {
		if (db == null)
			return false;
		synchronized (deleteStat) {
			deleteStat.bindString(1, cacheInfo.url);
			try {
				deleteStat.execute();
				count.decrementAndGet();
				size.addAndGet(-cacheInfo.size);
				return true;
			} catch (SQLException e) {
				return false;
			}
		}
	}

	@Override
	public boolean insert(CacheInfo cacheInfo) {
		if (db == null)
			return false;
		synchronized (insertHelper) {
			insertHelper.prepareForInsert();
			insertHelper.bind(iUrl, cacheInfo.url);
			insertHelper.bind(iVal, encode(cacheInfo));
			insertHelper.bind(iSize, cacheInfo.size);
			insertHelper.bind(iCreate, trimCreate(cacheInfo.create));
			if (insertHelper.execute() < 0) {
				return false;
			} else {
				count.incrementAndGet();
				size.addAndGet(cacheInfo.size);
				return true;
			}
		}
	}

	@Override
	public String getName() {
		return cacheName;
	}

	@Override
	public int getCount() {
		return count.get();
	}

	@Override
	public int getSize() {
		return size.get();
	}

	@SuppressWarnings("unchecked")
	DataCursor<CacheInfo> getIterator(String orderBy) {
		if (db == null)
			return DataCursor.EMPTY_CURSOR;
		String query = "SELECT " + FD_URL + ", " + FD_VAL + " FROM "
				+ cacheName + " ORDER BY " + orderBy + " ASC";
		Cursor c = db.rawQuery(query, null);
		return new DatabaseCursor<CacheInfo>(c) {
			@Override
			protected CacheInfo getData(Cursor c) {
				return decode(c.getString(0), c.getLong(1));
			}
		};
	}

	@Override
	public DataCursor<CacheInfo> iteratorByCreate() {
		return getIterator(FD_CREATE);
	}

	@Override
	public DataCursor<CacheInfo> iteratorBySize() {
		return getIterator(FD_SIZE);
	}

	@Override
	public void close() {
		if (db != null) {
			db = null;
			insertHelper.close();
			insertHelper = null;
			deleteStat.close();
			deleteStat = null;
			queryStat.close();
			queryStat = null;
		}
	}

	@Override
	public synchronized boolean clear() {
		clearStat.execute();
		return true;
	}

	@Override
	public String toString() {
		return "CacheInfoManagerImpl, size=" + getSize() + ", count="
				+ getCount();
	}

}
